package com.audaque.springboot.foshanupload.redis.aspect;

import com.audaque.springboot.foshanupload.redis.anno.IntegrationRedisLockAnno;
import com.audaque.springboot.foshanupload.redis.anno.RedisLuaIncrRateLimiterAnno;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

@Slf4j
@Aspect
@Component
public class IntegrationRedisLockAspect {

    @Autowired
    private RedisLockRegistry redisLockRegistry;



    @Pointcut("@annotation(com.audaque.springboot.foshanupload.redis.anno.IntegrationRedisLockAnno)")
    public void pointcutName() {
    }

    @Around("pointcutName()")
    public Object around(ProceedingJoinPoint joinPoint) {

        Object output = null;
        try {
            MethodSignature  signature = (MethodSignature)joinPoint.getSignature();
            //使用Java 反射技术获取方法上是否有@RedisRateLimiter 注解类
            IntegrationRedisLockAnno integrationRedisLockAnno = signature.getMethod().getDeclaredAnnotation(IntegrationRedisLockAnno.class);
            if(integrationRedisLockAnno == null){
                //正常执行方法,执行正常业务逻辑
                return joinPoint.proceed();
            }
            Method method = signature.getMethod();
            Object[] arguments = joinPoint.getArgs();

            // 获取锁的key
            Object lockKey = parseSpel(integrationRedisLockAnno.lockKey(), method, arguments);
            Lock lock = redisLockRegistry.obtain(lockKey);

            try {
                boolean ifLock = lock.tryLock(3, TimeUnit.SECONDS);
                log.debug("线程[{}]是否获取到了锁：{}", Thread.currentThread().getName(), ifLock);

                /*
                 * 可以获取到锁，说明当前没有线程在执行该方法
                 */
                if (ifLock) {
                    arguments[1] = true;
                }
                else {
                    arguments[1] = false;
                }

                output = joinPoint.proceed(arguments);
            }
            catch (Exception e) {
                log.error("执行核心奖励扫描时出错:{}", e.getMessage());
            }
            finally {
                log.debug("尝试解锁[{}]", lockKey);
                try {
                    lock.unlock();
                    log.debug("[{}]解锁成功", lockKey);
                }
                catch (Exception e) {
                    log.error("解锁dealAction出错:{}", e.getMessage());
                }
            }
        }
        catch (Throwable e) {
            log.error("aop redis distributed lock error:{}", e.getLocalizedMessage());
        }
        return output;
    }
    /**
     * 解析SpEL表达式
     * @param key SpEL表达式
     * @param method 反射得到的方法
     * @param args 反射得到的方法参数
     * @return 解析后SpEL表达式对应的值
     */
    public static Object parseSpel(String key, Method method, Object[] args) {
        // 创建解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 通过Spring的LocalVariableTableParameterNameDiscoverer获取方法参数名列表
        LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        // 构造上下文
        EvaluationContext context = new StandardEvaluationContext();
        if (args.length == parameterNames.length) {
            for (int i = 0, len = args.length; i < len; i++) {
                // 使用setVariable方法来注册自定义变量
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        return parser.parseExpression(key).getValue(context);
    }

}
